package simpleftp.client.control;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable; //---Implemented
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath; //---Implemented

import simpleftp.client.gui.FTPClientWindow;

public class CommandDispatcher implements Runnable
{
	public static final String NL = "\n";
	private static final String USER = "USER";
	private static final String PASS = "PASS";
	private static final String SERVER = "SERVER";
	private static final String PORT = "PORT";
	private static final String SETTINGS_ROOT = "simpleftpsettings";
	private static final String DEFAULT_SETTINGS = SETTINGS_ROOT + "/default";
	private static final String LAST_SETTINGS = SETTINGS_ROOT + "/last";
	private static final String DEF_USER = "paris";
	private static final String DEF_PASS = "paris";
	private static final String DEF_SERVER = "127.0.0.1";
	private static final String DEF_PORT = "7802";
	private final ExecutorService executorPool;
	private final CompletionService<Boolean> completionPool;
	private final FTPClientWindow window;
	private final int numberOfCores;
	private final double blockingCoefficient = 0.9;
	private final int poolSize;
	private BlockingQueue<Command> commands;
	private SimpleFTP client;
	private Preferences userPrefs;
	private String server;
	private String port;
	private String user;
	private String pass;
	private boolean alreadyConnected;

	public CommandDispatcher(BlockingQueue<Command> commandQueue, FTPClientWindow window) 
	{
		this.commands = commandQueue;
		this.window = window;
		alreadyConnected = false;
		numberOfCores = Runtime.getRuntime().availableProcessors();
		poolSize = (int) (numberOfCores / (1 - blockingCoefficient));
		executorPool = Executors.newFixedThreadPool(poolSize);
		completionPool = new ExecutorCompletionService<Boolean>(executorPool);
		client = new SimpleFTP();
	}

	@Override
	public void run() 
	{
		try {
				loadStartupPreferences();
				Command cmd = this.commands.take();
				while (!cmd.equals(Command.QUIT)) 
				{
					switch (cmd) 
					{
						case CONNECT:
							connect();
							break;
						case DISCONNECT:
							disconnect();
							break;
						case STOR:
							upload();
							break;
						case LIST:
							listDir();
							break;
						case PWD:
							pwd();
							break;
						case CLEAR:
							clearConsole();
							break;
						case LOADPREFS:
							loadPreferences();
							break;
						case SAVEPREFS:
							savePreferences();
							break;
						case DELPREF:
							deletePreference();
							break;
						//---Adding case click event on button "Download"
						case RETR:
							download();
							break;
						//---Adding case click event on button "CWD"
						case CWD:
							cwd();
							break;
						//---Adding case click event on button "Parent Directory"
						case PARENT:
							goParentDir();
							break;		
						default:
							break;
					}
				cmd = this.commands.take();
			}
			saveLastPreferences();
			disconnect();
			executorPool.shutdown();
		} 
		catch (InterruptedException e) 
		{
			e.printStackTrace();
		} 
		catch (Exception e) 
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private void clearConsole() 
	{
		window.console.setText("");
	}

	private void saveLastPreferences() 
	{
		userPrefs = Preferences.userRoot().node(LAST_SETTINGS);
		userPrefs.put(USER, window.tfusername.getText());
		userPrefs.put(PASS, new String(window.pfuserpass.getPassword()));
		userPrefs.put(SERVER, window.tfservername.getText());
		userPrefs.put(PORT, window.tfport.getText());
		try 
		{
			userPrefs.flush();
		} 
		catch (BackingStoreException e) 
		{
			e.printStackTrace();
		}
	}

	private void loadStartupPreferences() 
	{
		try 
		{
			userPrefs = Preferences.userRoot();
			if (!userPrefs.nodeExists(DEFAULT_SETTINGS)) {
				userPrefs = Preferences.userRoot().node(DEFAULT_SETTINGS);
				userPrefs.put(USER, DEF_USER);
				userPrefs.put(PASS, DEF_PASS);
				userPrefs.put(SERVER, DEF_SERVER);
				userPrefs.put(PORT, DEF_PORT);
				userPrefs.flush();
			}
			if (userPrefs.nodeExists(LAST_SETTINGS)) {
				userPrefs = Preferences.userRoot().node(LAST_SETTINGS);
			} 
			window.tfusername.setText(userPrefs.get(USER, DEF_USER));
			window.pfuserpass.setText(userPrefs.get(PASS, DEF_PASS));
			window.tfservername.setText(userPrefs.get(SERVER, DEF_SERVER));
			window.tfport.setText(userPrefs.get(PORT, DEF_PORT));
		} 
		catch (BackingStoreException e) 
		{
			e.printStackTrace();
		}
	}
	
	private void loadPreferences() {
		try {
			userPrefs = Preferences.userRoot().node(SETTINGS_ROOT);
			String[] userSavedPrefs = userPrefs.childrenNames();
			String s = (String) JOptionPane.showInputDialog(window,
					"Choose preference to load:", "Load Preferences",
					JOptionPane.QUESTION_MESSAGE, null, userSavedPrefs, userSavedPrefs[0]);
			if ((s != null) && (s.length() > 0)) {
				userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
				window.tfusername.setText(userPrefs.get(USER, DEF_USER));
				window.pfuserpass.setText(userPrefs.get(PASS, DEF_PASS));
				window.tfservername.setText(userPrefs.get(SERVER, DEF_SERVER));
				window.tfport.setText(userPrefs.get(PORT, DEF_PORT));
				
				window.console.append(NL + "User preferences " + s + " loaded.");
			} else {
				window.console.append(NL + "Preferences loading operation cancelled.");
			}
		} catch (BackingStoreException e) {
			e.printStackTrace();
		}
	}

	private void savePreferences() {
		String s = (String) JOptionPane.showInputDialog(window,
				"Save the current connection info as:", "Save Preferences",
				JOptionPane.QUESTION_MESSAGE, null, null, null);
		if ((s != null) && (s.length() > 0)) {
			userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
			userPrefs.put(USER, window.tfusername.getText());
			userPrefs.put(PASS, new String(window.pfuserpass.getPassword()));
			userPrefs.put(SERVER, window.tfservername.getText());
			userPrefs.put(PORT, window.tfport.getText());
			try {
				userPrefs.flush();
			} catch (BackingStoreException e) {
				window.console.append(NL + "Could not save preferences: "
						+ e.getMessage());
				e.printStackTrace();
			}
			window.console.append(NL + "Preferences saved as " + s);
		} else {
			window.console.append(NL + "Preferences saving operation cancelled.");
		}
	}
	
	private void deletePreference() {
		try {
			userPrefs = Preferences.userRoot().node(SETTINGS_ROOT);
			String[] userSavedPrefs = userPrefs.childrenNames();
			String s = (String) JOptionPane.showInputDialog(window,
					"Choose preference to delete:", "Delete Preference",
					JOptionPane.QUESTION_MESSAGE, null, userSavedPrefs, userSavedPrefs[0]);
			if ((s != null) && (s.length() > 0)) {
				userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
				userPrefs.removeNode();
				userPrefs.flush();
				window.console.append(NL + "User preferences " + s + " deleted.");
			} else {
				window.console.append(NL + "Preferences deletion operation cancelled.");
			}
		} catch (BackingStoreException e) {
			e.printStackTrace();
		}
	}
	
	//---method download()
	private void download() 
	{
		//window.console.append(NL + "Command not yet supported");
		//throw new UnsupportedOperationException("Download operation to be implemented...");
		if (!alreadyConnected)
		{
			window.console.append(NL + "You are not connected to any server.");
			return;
		}
		
		TreePath[] path = window.tree.getSelectionPaths();
		if (path!=null) 
		{
			List<File> files = new ArrayList<File>();
			for (TreePath tp: path )
			{
				files.add(new File (tp.getLastPathComponent().toString()));
			}
			List<Future<Boolean>> downloaders = new ArrayList<Future<Boolean>>(files.size());
			for (File file : files) 
			{
				Downloader downloader = new Downloader (file, window, server, port, user, pass);
				downloaders.add(completionPool.submit(downloader));
			}
		}
		else
		{
			window.console.append(NL + "Download action cancelled.");
		}
	}

	private void upload() 
	{
		if (!alreadyConnected) {
			window.console.append(NL + "You are not connected to any server.");
			return;
		}
		final JFileChooser fc = new JFileChooser();
		fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
		fc.setMultiSelectionEnabled(true);
		fc.setDragEnabled(true);
		int returnVal = fc.showDialog(window, "Upload");
		if (returnVal == JFileChooser.APPROVE_OPTION) 
		{
			File[] files = fc.getSelectedFiles();
			List<Future<Boolean>> uploaders = new ArrayList<Future<Boolean>>(files.length);
			for (File file : files) 
			{
				Uploader uploader = new Uploader (file, window, server, port, user, pass);
				uploaders.add(completionPool.submit(uploader));
			}
		}
		else 
		{
			window.console.append(NL + "Upload action cancelled.");
		}
	}

	private void listDir() throws Exception 
	{
		if (!alreadyConnected) 
		{
			window.console.append(NL + "You are not connected to any server.");
			return;
		}
		try {
			String dirContent = client.ls();
			window.console.append(NL + dirContent);
			// TODO complete / rewrite to get the file tree from the server.
			DefaultMutableTreeNode f = client.getFileTree();
			window.root.removeAllChildren();
			window.root.add(f);
			window.tree.updateUI();
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			e.printStackTrace();
		}
	}
	
	private void pwd() {
		try {
			if (!alreadyConnected) {
				window.console.append(NL + "You are not connected to any server.");
				return;
			}
			String curDir = client.pwd();
			window.console.append(NL + "Current directory on FTP server: " + curDir);
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			e.printStackTrace();
		}
	}

	private void disconnect() {
		try {
			if (alreadyConnected) {
				client.disconnect();
				alreadyConnected = false;
				window.console.append(NL + "Now disconnected");
			} else {
				window.console.append(NL + "Already disconnected");
			}
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			e.printStackTrace();
		}
	}

	private void connect() throws Exception {
		if (alreadyConnected) {
			window.console.append(NL
					+ "You are already connected to this server.");
			return;
		}
		server = window.tfservername.getText();
		if (server == null || "".equals(server)) {
			window.console.append(NL
					+ "You must specify the server IP address.");
			return;
		}
		port = window.tfport.getText();
		if (port == null || "".equals(port)) {
			window.console.append(NL
					+ "You must specify the server port I must connect to.");
			return;
		}
		user = window.tfusername.getText();
		if (user == null || "".equals(user))
			user = "anonymous";
		pass = new String(window.pfuserpass.getPassword());
		if (pass == null || "".equals(pass))
			pass = "anonymous";
		try {
			client.connect(server, Integer.parseInt(port), user, pass);
			alreadyConnected = true;
			window.console.append(NL + "Now connected");
			pwd();
			listDir();
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			e.printStackTrace();
		} catch (NumberFormatException nfe) {
			window.console.append(NL + nfe.getMessage());
			nfe.printStackTrace();
		}
	}
	
	//---ADDED Method CWD
	private void cwd() throws Exception
	{
		if (!alreadyConnected)
		{
			window.console.append(NL + "You are not connected to any server.");
			return;
		}
		
		TreePath treePath = window.tree.getSelectionPath();
		if(treePath!=null)
		{
			DefaultMutableTreeNode tNode = (DefaultMutableTreeNode) treePath.getLastPathComponent();
			StringBuilder strBuild = new StringBuilder();
			int i; //---i=2 in order to avoid the 2 first directories 
			for(i=2;i<treePath.getPathCount();i++)
			{
				strBuild.append(treePath.getPathComponent(i).toString());
				strBuild.append(System.getProperty("file.separator").toString());
			}
			
			strBuild.deleteCharAt(strBuild.length()-1);
			boolean inCurDir=client.cwd(strBuild.toString());
			
			if(inCurDir)
			{
				window.console.append(NL + "Current directory on FTP server: " + inCurDir);
				listDir();
			}
			
			else window.console.append(NL + "You have selected the directory you are located in.");
		}
		
		else window.console.append(NL + "Please click on the directory to be redirected to.");
	}

	//---ADDED Method parentDir that performs a cd..
	private void goParentDir() throws Exception
	{
		if(!alreadyConnected)
		{
			window.console.append(NL + "You are not connected to any server.");
			return;
		}
		client.getFileTree();
		DefaultMutableTreeNode tNode=client.getFileTree();
		window.root.removeAllChildren(); //---empty the current tree on the view
		window.root.add(tNode); //---Add the freshly created Tree Node in the Tree
		window.tree.updateUI(); //---Update the client GUI
	}
}
